package com.example.miaosha.aop;

import com.alibaba.fastjson.JSONObject;
import com.example.miaosha.common.annotation.PassAnnotation;
import com.example.miaosha.common.constants.Constants;
import com.example.miaosha.common.token.PtUserContextHolder;
import com.example.miaosha.common.utils.IpUtil;
import com.example.miaosha.common.utils.UserRepositoryUtil;
import com.example.miaosha.common.utils.jwt.JwtUtil;
import com.example.miaosha.common.utils.redis.RedisUtil;
import com.example.miaosha.entity.base.BaseResponse;
import com.example.miaosha.entity.user.PtUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;

/**
 * @Author
 * @Date 2021/12/28 19:40
 */
@Slf4j
@Aspect
@Component
public class AuthenticationAop {

    @Resource
    private UserRepositoryUtil userRepositoryUtil;

    @Resource
    private JwtUtil jwtConfig;

    @Resource
    private RedisUtil redisUtil;


    /**
     * 切面,定义拦截规则
     */
    @Pointcut("execution(public * com.example.miaosha.controller..*.*(..))")
    public void controllerMethodPointcut() {
    }

    /**
     * 环绕通知
     * 拦截器
     *
     * @param proceedingJoinPoint
     * @return
     */
    @Around("controllerMethodPointcut()")
    public Object Interceptor(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 拿到时间
        long startTime = System.currentTimeMillis();
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        //获取被拦截的方法
        Method method = signature.getMethod();
        // 方法名
        String name = method.getName();
        String classMethod = method.getDeclaringClass().getName() + "." + name + "()";
        // 拿到请求Request
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest httpRequest = attributes.getRequest();
        // 当前用户
        PtUser user = null;
        // RUI
        String uri = httpRequest.getRequestURI();
        // TOKEN
//        String token = null;
        String userId = null;
        // 返回结果
        Object result = null;
        // 保存所有请求参数，用于输出到日志中
        Set<Object> params = new LinkedHashSet<>();
        // 拿到参数
        Object[] args = proceedingJoinPoint.getArgs();
        try {
            // startsWith 是否以com前缀开头
            if (uri.startsWith(Constants.COM_INTERFACE_PREFIX) || method.isAnnotationPresent(PassAnnotation.class)) {
                // 如果是以com开头，或者有 PassAnnotation 注解修饰的方法，就直接放行
                result = proceedingJoinPoint.proceed(args);
                return result;
            } else {
                // 后台请求
                userId = httpRequest.getHeader(Constants.USER_ID);
                if (StringUtils.isNotBlank(userId)) {
                    // 如果请求头中有Token，那就根据token拿到用户信息
                    user = resolvePartnerAdminToken(userId, httpRequest);
                }
                if (Objects.isNull(user)) {
                    return BaseResponse.authorize("token过期或无效,请确认携带了对应的Token");
                }
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            log.warn("AOP内用户认证出现异常");
            return BaseResponse.authorize(e.getMessage());
        }

        // TODO  存入 ThreadLocal 中
        PtUserContextHolder.set(user);

        try {
            // 处理重复刷新问题
//            Integer time = (Integer) redisUtil.get(RedisEnums.FREQUENCY_LIMIT_KEY + "_" + name +"_" + user.getId());
//            if(Objects.nonNull(time)){
//                if(time>0){
//                    redisUtil.set(RedisEnums.FREQUENCY_LIMIT_KEY +"_"+ name +"_"+ user.getId(),time-1,3);
//                }else {
//                    throw new BusinessException("刷新频率过快");
//                }
//            }else {
//                // 默认一秒内3次刷新为基本范围
//                redisUtil.set(RedisEnums.FREQUENCY_LIMIT_KEY+"_"+name+"_"+user.getId(),2,3);
//            }
            log.info("请求URI：{}，目标方法：{}，请求参数：{}", uri, classMethod, JSONObject.toJSONString(params));
            // 执行方法
            result = proceedingJoinPoint.proceed(args);
            log.info("请求URI：{}，目标方法：{}，返回参数：{}", uri, classMethod, JSONObject.toJSONString(result));
            long costMs = System.currentTimeMillis() - startTime;
            log.info("{}请求结束，耗时：{}ms", classMethod, costMs);
        } finally {
            // 移除
            PtUserContextHolder.clear();
        }
        return result;
    }


    /**
     * 根据 Token 拿到 user 信息
     *
     * @param userId
     * @return
     */
    private PtUser resolvePartnerAdminToken(String userId, HttpServletRequest httpRequest) {
        PtUser user = null;
        if (StringUtils.isNoneBlank(userId)) {
            // 过期时间
//            long exp = jwtConfig.getTokenExpiresAt(token);
            // 手机号码
//            String phone = jwtConfig.getToken(token);
//            if (System.currentTimeMillis() / 1000 < exp) {
            user = new PtUser();
//                user.setToken(token);
//                user.setPhone(phone);
            user.setIp(IpUtil.getIpAddr(httpRequest));
            user.setId(userId);
            user = userRepositoryUtil.querySysUserDetail(user);
            if (Objects.nonNull(user)) {
                return user;
            }
//            } else {
//                log.error("token过期了,请重新获取,token: {}", token);
//            }
        }
        return null;
    }

}
